/**
 * Copyright 2009 Henning Teek, Ingo Renner
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.chigs.web.doku.revision;

import java.io.Serializable;
import java.util.List;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.authorization.strategies.role.annotations.AuthorizeInstantiation;
import org.apache.wicket.extensions.markup.html.form.palette.Palette;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.form.upload.FileUpload;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.request.target.basic.RedirectRequestTarget;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brix.BrixNodeModel;
import brix.web.nodepage.BrixNodeWebPage;
import brix.web.nodepage.BrixPageParameters;

import com.googlecode.chigs.domain.Annotation;
import com.googlecode.chigs.domain.Attachment;
import com.googlecode.chigs.domain.ConfigLocation;
import com.googlecode.chigs.domain.ConfigPair;
import com.googlecode.chigs.domain.GenericScript;
import com.googlecode.chigs.domain.Module;
import com.googlecode.chigs.domain.Product;
import com.googlecode.chigs.domain.Revision;
import com.googlecode.chigs.domain.Version;
import com.googlecode.chigs.repository.ChigsFacade;
import com.googlecode.chigs.web.ChigsApplication;
import com.googlecode.chigs.web.doku.annotation.EditAnnotationFormularPanel;
import com.googlecode.chigs.web.doku.attachment.EditAttachmentFormularPanel;
import com.googlecode.chigs.web.doku.configpair.EditConfigPairFormularPanel;
import com.googlecode.chigs.web.doku.genericscript.EditGenericScriptFormularPanel;
import com.googlecode.chigs.web.doku.version.VersionLabel;
import com.googlecode.chigs.web.doku.version.VersionSelector;
import com.googlecode.chigscommons.repository.GenericDomainService;
import com.googlecode.chigscommons.repository.StorehouseService;
import com.googlecode.chigscommons.repository.UncheckedRuntimeException;
import com.googlecode.chigscommons.web.WicketWebSession;
import com.googlecode.chigscommons.web.common.IconFactory;
import com.googlecode.chigscommons.web.common.forms.ShinyForm;
import com.googlecode.chigscommons.web.common.modal.AModalManagement;
import com.googlecode.chigscommons.web.common.modal.UploadPanel;
import com.googlecode.chigscommons.web.common.table.AjaxFallbackDefaultDataTableBuilder;

/**
 * 
 * @author Ingo Renner
 * @author Henning Teek
 *
 */
@AuthorizeInstantiation("ROLE_USER")
public class EditOrCreateRevisionPanel extends Panel {
	private static final Logger log = LoggerFactory.getLogger(EditOrCreateRevisionPanel.class);

	@SpringBean
	private ChigsFacade chigs;
	
	@SpringBean
	private StorehouseService storehouseService;

	@SpringBean
	private GenericDomainService genericDomainService;
	
	private final Product product;

	private final Module module;

	private AModalManagement<Annotation> annotationManagement;
	
	private AModalManagement<ConfigPair> configPairManagement;

	private AModalManagement<GenericScript> genericScriptManagement;

	private AModalManagement<Attachment> attachmentManagement;
	
	final CompoundPropertyModel<Revision> model;

	private final EditOrCreateRevisionForm editOrCreateRevisionForm;


	
	/**
	 * Ein neues Module anlegen 
	 * @param id Wicket Id
	 * @param product Zugehöriges Produkt
	 */
	public EditOrCreateRevisionPanel(String id, Product product) {
		this(id, product, null);
	}

	/**
	 * Ein Module überarbeiten
	 * @param id Wicket Id
	 * @param product Zugehöriges Produkt
	 * @param module Zu bearbeitendes Module
	 */
	public EditOrCreateRevisionPanel(String id, Product product, Module module) {
		super(id);
		this.product = product;
		this.module = module;

		Revision revision;
		
		if (module == null) {
			revision = new Revision();
		} else {			
			revision = chigs.loadLatestRevision(module);
		}
		
		revision.setUser(((WicketWebSession) WicketWebSession.get()).getUser());

		this.model = new CompoundPropertyModel<Revision>(revision);
		setDefaultModel(model);
		
		editOrCreateRevisionForm = new EditOrCreateRevisionForm("form", this.model, true);

		add(editOrCreateRevisionForm);
		initManagement();
	}

	public EditOrCreateRevisionPanel(String id, Product product, Module module, Revision revision) {
		super(id);
		this.product = product;
		this.module = module;
		
		revision = genericDomainService.load(Revision.class, revision.getId());

		this.model = new CompoundPropertyModel<Revision>(revision);
		editOrCreateRevisionForm = new EditOrCreateRevisionForm("form", this.model, false);

		add(editOrCreateRevisionForm);
		initManagement();
	}

	private void initManagement() {
		add(getAnnotationManagement().getEditor());
		add(getConfigPairManagement().getEditor());
		add(getGenericScriptManagement().getEditor());
		add(getAttachmentManagement().getEditor());
	}
	
	class EditOrCreateRevisionForm extends ShinyForm<Revision> {
		private final boolean editable;

		public EditOrCreateRevisionForm(String id, IModel<Revision> model, boolean editable) {
			super(id, model);
			this.editable = editable;
			StringResourceModel label = new StringResourceModel("name.label", EditOrCreateRevisionForm.this, null);
			add(new Label("name.label", label));
			add(new TextField<String>("module.name")
					.setRequired(true)
					.setLabel(label)
					.setEnabled(editable));
			add(new Label("nr"));
			
			// Version auswählen und evtl neue anlegen 
			boolean editVersion = false;
			if (getModelObject().getVersion() != null && !getModelObject().getVersion().isNew()) {
				// Es ist eine Version eingetragen worden, nur der Admin darf das jetzt ändern
				editVersion = ((WicketWebSession) getSession()).getUser().isAdmin();
			} else {
				// Version auswählen, mit der Möglichkeit, einen neue kurzfristig anlegen zu können.
				editVersion = true;
			}
			if (editVersion && editable){
				add(new VersionSelector("version", new PropertyModel<Version>(getModelObject(), "version"), product){
					@Override
					protected void onChange(AjaxRequestTarget target) {
						if (log.isDebugEnabled()) {
							log.debug("onChange: Version is now:" + getModelObject().getVersion().getVersion());
						}						
					}					
				});
			} else {
				add(new VersionLabel("version", new Model<Version>(getModelObject().getVersion())));
			}
			
			// Abhängigkeiten verwalten
			PropertyModel<List<Module>> cur = new PropertyModel<List<Module>>(getModelObject(),"dependencies");
			List<Module> available = chigs.loadModulesByProduct(product);
			available.remove(getModelObject().getModule());
			Palette<Module> palette = new Palette<Module>(
					"dependencies", cur,
					new Model((Serializable) available),
					new ChoiceRenderer<Module>("name", "id"), 10, false
			);
			palette.setEnabled(editable);
			add(palette);
			
			getAnnotationManagement().setEditMode(editable);
			getAnnotationManagement().setDelete(editable);
			add(getAnnotationManagement().getPresenter());
			add(getAnnotationManagement().getAddLink());
			
			getConfigPairManagement().setEditMode(editable);
			getConfigPairManagement().setDelete(editable);
			add(getConfigPairManagement().getPresenter());
			add(getConfigPairManagement().getAddLink());
			
			getGenericScriptManagement().setEditMode(editable);
			getGenericScriptManagement().setDelete(editable);
			add(getGenericScriptManagement().getPresenter());
			add(getGenericScriptManagement().getAddLink());
			
			getAttachmentManagement().setEditMode(editable);
			getAttachmentManagement().setDelete(editable);
			add(getAttachmentManagement().getPresenter());
			add(uploadBeforeAttachmentMngLink());

			// Formular Ende, submit (im Html) und Cancel
			SubmitLink submitLink = new SubmitLink("submitButton");
			submitLink.add(new Image("icon", IconFactory.createOkIconReference()));
			add(submitLink.setVisible(editable));
			
			Link<Void> cancelLink = new Link<Void>("cancelButton") {
				@Override
				public void onClick() {
					returnToManagement();
				}
			};
			cancelLink.add(new Image("icon", IconFactory.createCancelIconReference()));
			add(cancelLink);
		}

		// Attachments hinzufügen
		private Component uploadBeforeAttachmentMngLink() {
			UploadPanel up = new UploadPanel<Attachment>("attachmentUpload") {
				@Override
				public String onFileUploaded(FileUpload upload) {
					if (upload != null){
						try {
							final String digest = storehouseService.doImport(upload.getInputStream(), upload.getMD5());
							if (digest != null) {
								// kein feddbackpannel verfügbar, erstmal ins log
								log.info("Upload ok");
								final Attachment a = new Attachment();
								a.setFilename(upload.getClientFileName());
								a.setSize(upload.getSize());
								a.setHash(digest);
								// Store in static Map to carry it to onUploadFinished callback
								putUploadResult(a.getFilename(), a);
							} else {
								// kein feddbackpannel verfügbar, erstmal ins log
								log.error("Datei konnte nicht erfolgreich gespeichert werden.");
							}
						} catch (Exception e) {
							log.error(e.getMessage(),e);
						}
					} else {					
						// kein feddbackpannel verfügbar, erstmal ins log
						log.error("Bitte erst eine Datei auswählen.");
					}
					return "";
				}

				@Override
				public void onUploadFinished(AjaxRequestTarget target, Attachment a) {
					//when upload is finished, will be called
					EditOrCreateRevisionForm.this.modelChanging();
					log.debug("onUploadFinished old= "+ ((Revision) EditOrCreateRevisionForm.this.getDefaultModelObject()).getAttachments().toString());
					log.debug("Adding attachment " + a);
					((Revision) EditOrCreateRevisionForm.this.getDefaultModelObject()).getAttachments().add(a);
					log.debug("The attachments now= "+ ((Revision) EditOrCreateRevisionForm.this.getDefaultModelObject()).getAttachments().toString());
					log.debug(EditOrCreateRevisionPanel.this.getDefaultModelObject().toString());
					getAttachmentManagement().setEditMode(editable);
					getAttachmentManagement().setDelete(editable);
					EditOrCreateRevisionForm.this.addOrReplace(getAttachmentManagement().getPresenter());
					log.debug("On finished attachments = "+ ((Revision) EditOrCreateRevisionForm.this.getDefaultModelObject()).getAttachments().toString());
					target.addComponent(getAttachmentManagement().getPresenter());
				}
			};
			up.setOutputMarkupId(true);
			up.setVisible(editable);
			return up;
		}
		
		@Override
		protected void onSubmit() {
			try {
				final Revision revision = getModelObject();
				if (log.isDebugEnabled()) {
					log.debug("onSubmit() revision: " + revision + " annotations: " + revision.getAnnotations());
					log.debug("onSubmit() revision: " + revision + " config pairs: " + revision.getConfigPairs());
					log.debug("onSubmit() revision: " + revision + " generic scripts: " + revision.getGenericScripts());
					log.debug("onSubmit() revision: " + revision + " attachments: " + revision.getAttachments());
				}
				if (editable) {
					chigs.saveRevision(revision);
					getSession().info(new StringResourceModel("save.success", EditOrCreateRevisionForm.this, null).getString());
				}
				returnToManagement();
			} catch (Exception e) {
				log.error(e.toString());
				error(e.getMessage() == null ? e.toString() : e.getMessage());
			}
		}
	}
	
	public AModalManagement<Annotation> getAnnotationManagement() {
		if (annotationManagement == null) {
			annotationManagement = new AModalManagement<Annotation>("annotationPresenter", "annotationEditor", "annotationCreate") {
				@Override
				protected List<Annotation> getValues() {					
					return getModel().getObject().getAnnotations();
				}

				@Override
				public Annotation getNewObject() {
					return new Annotation();
				}

				@Override
				protected MarkupContainer getPresenterParent() {
					return editOrCreateRevisionForm;
				}

				@Override
				protected MarkupContainer getEditorParent() {
					return EditOrCreateRevisionPanel.this;
				}

				@Override
				protected Component fillForm(String id, IModel<Annotation> model) {
					return new EditAnnotationFormularPanel(id, model);
				}

				@Override
				protected void addColumns(AjaxFallbackDefaultDataTableBuilder builder) {
					builder.addPropertyColumn("headline", false)
					.addPropertyColumn("text", false)
					.setNumberOfRows(10);
				}

				@Override
				protected void notifyValueChanged(Annotation currentObject) {
					super.notifyValueChanged(currentObject);
					if (log.isDebugEnabled()) {
						log.debug("Annotations in revision object:");
						for (Annotation a : getModel().getObject().getAnnotations()) {
							log.debug(a.toString());	
						}
					}
				}

				@Override
				protected void notifyValueToAdd(Annotation currentObject) {
					super.notifyValueToAdd(currentObject);
					if (log.isDebugEnabled()) {
						log.debug("Annotations in revision object:");
						for (Annotation a : getModel().getObject().getAnnotations()) {
							log.debug(a.toString());	
						}
					}
				}
				
			};
		}
		return annotationManagement;
	}
	
	public AModalManagement<ConfigPair> getConfigPairManagement() {
		if (configPairManagement == null) {
			configPairManagement = new AModalManagement<ConfigPair>("configPairPresenter", "configPairEditor", "configPairCreate") {
				@Override
				protected List<ConfigPair> getValues() {					
					return getModel().getObject().getConfigPairs();
				}

				@Override
				public ConfigPair getNewObject() {
					return new ConfigPair();
				}

				@Override
				protected MarkupContainer getPresenterParent() {
					return editOrCreateRevisionForm;
				}

				@Override
				protected MarkupContainer getEditorParent() {
					return EditOrCreateRevisionPanel.this;
				}

				@Override
				protected Component fillForm(String id, IModel<ConfigPair> model) {
					List<ConfigLocation> locations = chigs.loadLocationsByVersion(EditOrCreateRevisionPanel.this.getModel().getObject().getVersion());
					return new EditConfigPairFormularPanel(id, model, locations);
				}

				@Override
				protected void addColumns(AjaxFallbackDefaultDataTableBuilder builder) {
					builder.addPropertyColumn("name", true)
					.addBooleanPropertyColumn("mandatory", true)
					.addPropertyColumn("type", true)
					.addPropertyColumn("defaultData", true)
					.addPropertyColumn("configLocation.name", true)
					.setNumberOfRows(10);
				}

				@Override
				protected void notifyValueChanged(ConfigPair currentObject) {
					super.notifyValueChanged(currentObject);
					if (log.isDebugEnabled()) {
						log.debug("Config pairs in revision object:");
						for (ConfigPair a : getModel().getObject().getConfigPairs()) {
							log.debug(a.toString());	
						}
					}
				}

				@Override
				protected void notifyValueToAdd(ConfigPair currentObject) {
					super.notifyValueToAdd(currentObject);
					if (log.isDebugEnabled()) {
						log.debug("Config pairs in revision object:");
						for (ConfigPair a : getModel().getObject().getConfigPairs()) {
							log.debug(a.toString());	
						}
					}
				}
				
				@Override
				protected void prepareAdd(AjaxRequestTarget target) {
					if (getModel().getObject().getVersion().isNew()) {
						throw new UncheckedRuntimeException("No version specified");
					}
				}
			};
		}
		return configPairManagement;
	}

	public AModalManagement<GenericScript> getGenericScriptManagement() {
		if (genericScriptManagement == null) {
			genericScriptManagement = new AModalManagement<GenericScript>("genericScriptPresenter", "genericScriptEditor", "genericScriptCreate") {
				@Override
				protected List<GenericScript> getValues() {					
					return getModel().getObject().getGenericScripts();
				}

				@Override
				public GenericScript getNewObject() {
					return new GenericScript();
				}

				@Override
				protected MarkupContainer getPresenterParent() {
					return editOrCreateRevisionForm;
				}

				@Override
				protected MarkupContainer getEditorParent() {
					return EditOrCreateRevisionPanel.this;
				}

				@Override
				protected Component fillForm(String id, IModel<GenericScript> model) {
					return new EditGenericScriptFormularPanel(id, model);
				}

				@Override
				protected void addColumns(AjaxFallbackDefaultDataTableBuilder builder) {
					builder.addPropertyColumn("name", true)
					.addPropertyColumn("script", false)
					.setNumberOfRows(10);
				}

				@Override
				protected void notifyValueChanged(GenericScript currentObject) {
					super.notifyValueChanged(currentObject);
					if (log.isDebugEnabled()) {
						log.debug("Generic scripts in revision object:");
						for (GenericScript a : getModel().getObject().getGenericScripts()) {
							log.debug(a.toString());	
						}
					}
				}

				@Override
				protected void notifyValueToAdd(GenericScript currentObject) {
					super.notifyValueToAdd(currentObject);
					if (log.isDebugEnabled()) {
						log.debug("Generic scripts in revision object:");
						for (GenericScript a : getModel().getObject().getGenericScripts()) {
							log.debug(a.toString());	
						}
					}
				}
				
			};
		}
		return genericScriptManagement;
	}

	public AModalManagement<Attachment> getAttachmentManagement() {
		if (attachmentManagement == null) {
			attachmentManagement = new AModalManagement<Attachment>("attachmentPresenter", "attachmentEditor", "attachmentCreate"){
				@Override
				protected List<Attachment> getValues() {					
					return getModel().getObject().getAttachments();
				}

				@Override
				public Attachment getNewObject() {
					return new Attachment();
				}

				@Override
				protected MarkupContainer getPresenterParent() {
					return editOrCreateRevisionForm;
				}

				@Override
				protected MarkupContainer getEditorParent() {
					return EditOrCreateRevisionPanel.this;
				}

				@Override
				protected Component fillForm(String id, IModel<Attachment> model) {
					// Attachment reicht nicht, wir brauchen auch die locations der aktuellen Version
					List<ConfigLocation> locations = chigs.loadLocationsByVersion(EditOrCreateRevisionPanel.this.getModel().getObject().getVersion());
					return new EditAttachmentFormularPanel(id, model, locations);
				}

				@Override
				protected void addColumns(AjaxFallbackDefaultDataTableBuilder builder) {
					builder
					.addFileDownloadPropertyColumn(false)
					.addPropertyColumn("size", true)
					.addPropertyColumn("description", false)
					.addPropertyColumn("hash", false)
					.addPropertyColumn("configLocation.name", true)
					.setNumberOfRows(10);
				}
			};
		}
		return attachmentManagement;
	}

	public CompoundPropertyModel<Revision> getModel() {
		return model;
	}

	private void returnToManagement() {
		BrixPageParameters params = new BrixPageParameters();
		params.addQueryParam("product", product.getId().toString());
		BrixNodeModel model = ((ChigsApplication) getApplication()).getI18nBrixLink(getSession(), "/documentation/modules.html");
		getRequestCycle().setRequestTarget(new RedirectRequestTarget(params.urlFor(new BrixNodeWebPage(model))));
	}
	
}
